/*
 * Concurrency
 * ================================================================================
 * Currently, almost everything runs in Swing's event dispatching thread.
 *
 * Ultimately, we would like to do something like the following sketch:
 * initial thread:  all it does is starting Swing
 * Swing's thread:  handles quit event and display rendered image from worker thread.  Doesn't handle computation itself.
 * worker thread:  handles both game logic and rendering
 * (Reason is to prevent high load from freezing the window. At least we want to quit button to be responsive.)
 *
 *
 * Modules
 * ================================================================================
 * Main      -  ties everything together
 * ECSWorld  -  stores the abstract game state (Entities)
 * GameState -  stores the abstract game state (everything else)
 * Renderer  -  turns entities into a bitmap buffer
 * GUI       -  display the bitmap buffer on screen.  also manages game window.
 * KeyBindings - maps keys (WSAD) to commands (moveleft, moveright, quit)
 *
 * It is not practical to fully compartmentalise Spring-specific code.
 * However, we try to minimize Spring dependency outside of GUI and Renderer.
 * The hope is that if we ever switch to JavaFX, JOGL, etc, we don't need to rewrite the entire repo.
 *
 *
 * TODO document coordinate systems
 * ================================================================================
 *
 *
 * TODO
 * ================================================================================
 * constantly update documentation
 * game coor vs window coor
 * license
 * test on Windows if we have time
 *
 * Roadmap:
 *   Start with a bruteforce implementation. As long as the API works, performance doesn't matter.
 *   Then implement a few strategies and see which works better. (archetypes, etc)
 */

/*
 * The above are technical documentations about the source code, etc.
 * The following documents the game logic.
 *
 * The world is 2D. Unless otherwise noted, time and position are discrete and represented by integers.
 *
 * ===== Time =====
 * The game runs at 60 frames per second (or something like that, depending on configuration).
 * As with any video games, the world changes from frame to frame in a discontinuous manner, although
 * it uses a few tricks to create the illusion of continuity.  We label the frames as t = 0, 1, 2, ...,
 * with t = 0 being the initial state of the world.  Unless otherwise noted, t = n refers to the state
 * at the beginning of frame n (which is also the state at the end of frame n-1).
 *
 * Sometimes, we would like to talk about the imaginary continuity between two consecutive frames.
 * Unless otherwise noted, we use a local timeline for this purpose, starting at t = 0 and ending at t = 1.
 * t in this context is a real number.  Anything happening in 0 < t < 1 is imaginary.  Depending on
 * specific game rules, they may or may not be respected.
 *
 * ===== Cells and Grid Locations =====
 * We divide the entire space into little squares called "cells" by drawing vertical lines at
 * x = -0.5, x = 0.5, x = 1.5, x = 2.5 ... and do similarly for horizontal lines y = -0.5, y = 0.5 and
 * so on.  Each cell is centered at an integer point called a "grid location" or other similar names.
 * Please don't confuse grid locations with the corners of grid cells.  When we want to talk about the
 * edges and corners of grid cells, we simply refer to them as such.
 *
 * ===== Paths =====
 * In our world view, object movements can be described by two paths: a pure path and a "rasterised path".
 *
 * Take linear movement for example.
 * The pure path is a line segment from point S to point E. This is a line segment in the
 * mathematical sense: perfectly smooth, perfectly straight, not limited by hardware precision.
 *
 * The quote-on-quote "rasterised path" is a series of discrete grid points, constructed by snapping
 * the pure path to a lattice.  Imagine a point traveling on the pure path over continuous time.
 * Most of the time, this point falls inside a grid cell.  We add the center of this cell to the
 * rasterised path.  Only points added in this process is on the rasterised path; no other points
 * are on the path.  It follows immediately that the path starts at S and ends at E, and every
 * next point is a neighbour (a king's move away) of the previous point.
 *
 * ===== Discreteness and Errors =====
 * At the beginning of each frame, every entity starts at a grid location (if they have a position to begin with).
 * At the end of each frame, they end at a grid location.  If, according to some motion rules, they would
 * have ended up at a real number coordinate, then we round that to a nearby integer coordinate.
 * Over long enough time, accumulated errors may become noticeable.  However, most of our motion systems
 * employ tricks to mitigate that.  If the player managed to construct niche examples that defeats our
 * error mitigation measures, then we will call it a feature.
 *
 * In fact, we actively make use of the discrete nature of grid cells to achieve certain properties.
 *
 * One of them is reproducibility: we make minimal use of floating point numbers, converting them back
 * to integers as soon as possible.  We do so in the hope that any inconsistencies between platforms
 * are likely cancelled before they manage to accumulate.  For example, C++ compilers don't necessarily
 * follow IEEE 754, so someone emulating our Java game in C++ may get different results.  By switching
 * back to integer immediately, we reduce the chance of that happening.  (To make sure that it never
 * ever happens would require much more work to be done, including handcrafted trigonometry functions
 * and so on.  Currently, we can't afford that.)
 * TODO We also plan to enforce game frames regardless of display framerate.
 *
 * The other is logical correctness (at the cost of numerical correctness).  For example, when an entity
 * is about to crash into a wall, it may stop at the last grid location outside the wall, even if that
 * location is farther away from the theoretical collision location than a point inside the wall is.
 * (Yep, we do this by tracing the rasterised path.)  As a result, an entity will almost never clip
 * inside a wall.  (It is possible, however, in a planned future version.  We plan to add moving walls,
 * which may or may not allow walls to actively clip into the entity, before the entity itself is able
 * to detect the collision. We will see. TODO update doc accordingly)
 *
 */

package nz.ac.massey.groupproject.ecs2;

import java.util.ArrayList;

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

import java.lang.reflect.InvocationTargetException;

public class Main {
	private static final GameState             globalData    = new GameState();
	private static final ECSWorld              world         = new ECSWorld(globalData);
	private static final ArrayList<ECSSystem>  pipeline      = new ArrayList<>();

	// We throw InvocationTargetException because it isn't supposed to happen.
	// If it happened anyways, there is a bug.  If there is a bug, we fail loudly.
	public static void main(String[] args)   throws InvocationTargetException {

		// create window & canvas
		try {
			GUI.setupGUI();

		} catch (InterruptedException e) { System.err.println("Game startup interrupted: " + e); System.exit(1); }

		// shell
		pipeline.add(new KeyBindings.TranslateKeypressesToCommands());

		// game logic
		globalData.registerSystems(pipeline);
		globalData.spawnTestEntities(world);

		pipeline.add(new ECSSystem() {
			public void globalTick(ECSWorld world) {
				world.compact();
				//System.err.println("num ent: " + world.entities.size());
			}
		});

		// main loop
		var callback = new ActionListener() {
			// runs in Swing's thread
			public void actionPerformed(ActionEvent unused) {

				ECSSystemUtil.smartTick(pipeline, world);

				var sz = GUI.getCanvasSize();
				var img = Renderer.render(sz, world);

				GUI.drawFrame(img);

			}
		};

		GUI.scheduleAtInterval(1000/60, callback);  // TODO deal with actual framerate (roughly 60 * 3600 frames after an hour, don't accumulate errors)
	}

	// Technically, we could put this in the game command queue and register an AppExitSystem.
	// The only reason we take QUIT handling out of game logic is one of mental clarity.
	static void issueCommand(UserCommand cmd) {
		if (UserCommand.QUIT == cmd)
			System.exit(0);

		globalData.pendingCmds.add(cmd);
	}

}
